/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 *
 */

import { hvigor, Project, TaskInputValue } from '@ohos/hvigor';
import { FileSet } from '@ohos/hvigor';
import * as fse from 'fs-extra';
import path from 'path';

import { BuildArtifactConst, BuildArtifactExtension } from '../const/build-directory-const.js';
import { CommonConst, DefaultTargetConst } from '../const/common-const.js';
import { Model } from '../model/model.js';
import { ModuleModel } from '../model/module/module-model.js';
import { ProjectBuildProfile } from '../options/build/project-build-profile.js';
import { PackInfo } from '../options/configure/pack-info-options.js';
import { OhosLogger } from '../utils/log/ohos-logger.js';

import { TaskNames } from './common/task-names.js';
import { ProjectTaskService } from './service/project-task-service.js';
import { OhosAppTask } from './task/ohos-app-task.js';
import CommonHookTask = TaskNames.CommonHookTask;
import { getJson5Obj } from '../utils/json-util.js';

import { ModuleTaskService } from './service/module-task-service.js';
import RemoteHspOpt = ProjectBuildProfile.RemoteHspOpt;
import { getAlignTarget } from '@ohos/hvigor/src/base/util/task-util';

import { initSignRemoteHspMap } from '../utils/remote-hsp-utils.js';
import { ZipUtil } from '../utils/zip-util.js';
import { DependencyManager } from '../project/dependency/dependency-manager.js';

const Task = TaskNames.Task;
const _log = OhosLogger.getLogger('hvigor-make-project-info');

/**
 * 生成Fa模型和Stage模型app级别的pack.info
 */
export class MakeProjectPackInfo extends OhosAppTask {
  private moduleSet: Set<string> = new Set<string>();
  private readonly hapPackInfoPathMap = new Map<string, string>();
  private readonly allRemoteHspPathMap: Map<string, RemoteHspOpt> = new Map<string, RemoteHspOpt>();

  private readonly remoteHspModuleSet: Set<string> = new Set<string>();

  declareInputs(): Map<string, TaskInputValue> {
    const inputs = new Map<string, TaskInputValue>();
    inputs.set('allRemoteHspPathMap', JSON.stringify([...this.allRemoteHspPathMap]));
    return inputs;
  }

  declareInputFiles(): FileSet {
    const inputFileSet = new FileSet();
    for (const hapPackInfoPath of this.hapPackInfoPathMap.keys()) {
      inputFileSet.addEntry(hapPackInfoPath);
    }
    return inputFileSet;
  }

  declareOutputFiles(): FileSet {
    const outputFilePath = path.resolve(this.service.getPathInfo().getProjectOutputPath(), BuildArtifactConst.PACK_INFO);
    return new FileSet().addEntry(outputFilePath);
  }

  // default value
  private _projectPackInfoObj: PackInfo.PackInfoObj = {
    summary: {
      app: {
        bundleName: 'bundleName',
        version: {
          name: 'name',
          code: 0,
        },
      },
      modules: [],
    },
    packages: [],
  };

  initTaskDepends() {
    hvigor.nodesEvaluated(() => {
      // 这里由于需要判断模块类型,所以调用的事ProjectModel的getSubModuleModels方法
      // 同时由于是先执行的module的configuration阶段,再执行的project的.因此此处获取的subModuleModel是全的
      const projectSubModuleModels = this.service.getProjectModel().getSubModuleModels();
      _log.debug(`project has submodules:${[...projectSubModuleModels.keys()]}`);
      projectSubModuleModels.forEach((value: Model) => {
        const moduleModel = value as ModuleModel;
        const isNeedPackageHap: boolean = this.isNeedPackageHap(moduleModel);
        if (moduleModel.isHapModule() && isNeedPackageHap) {
          this.dependsOn(CommonHookTask.ASSEMBLE_HAP.name!, value.getName());
        } else {
          _log.debug(`module:${value.getName()} no need to execute packageHap`);
        }
        if (moduleModel.isHspModule() && isNeedPackageHap) {
          this.dependsOn(CommonHookTask.ASSEMBLE_HSP.name!, value.getName());
        }
      });
    });
  }

  /**
   * 判断moduleModel对应的node模块是否存在一个Target需要打包到当前指定的product的app中
   * 判断的依据是在根项目的build-profile.json5中
   * 1.显示的配置了对应的target
   * 2.target的applyToProducts字段中包含了指定的product,默认如果没有配置applyToProducts则默认为applyTo default
   *
   * @param {ModuleModel} moduleModel
   * @returns {boolean}
   * @private
   */
  private isNeedPackageHap(moduleModel: ModuleModel): boolean {
    // 打开拉齐开关之后 即使不在product中的targetName也需要打包
    if (getAlignTarget()) {
      return true;
    }
    const targetProductName = this.service.getTargetProduct().name;
    const moduleProjectBuildOpt = this.service.getProjectModel().getModuleProfileOpt(moduleModel.getName());

    // 如果在根build-profile.json5中找不到对应模块或在该模块中没有配置任何targets则不需要执行该模块的打包Hap任务
    const targets: ProjectBuildProfile.ProjectTargetBuildOpt[] | undefined = moduleProjectBuildOpt?.targets;
    if (targets === undefined) {
      return false;
    }

    // targets中除了ohosTest外是否存在一个applyTo了指定的product
    return targets.some((targetOpt) => {
      const targetName = targetOpt.name;
      let targetApplyToProducts = targetOpt.applyToProducts;
      if (targetApplyToProducts === undefined) {
        targetApplyToProducts = ['default'];
      }
      return targetName !== DefaultTargetConst.OHOS_TEST_TARGET && targetApplyToProducts.includes(targetProductName);
    });
  }

  constructor(project: Project, taskService: ProjectTaskService) {
    super(project, taskService, Task.MAKE_PROJECT_PACK_INFO);
  }

  async beforeAlwaysAction() {
    const projectRemoteHspPath = this.service.getProjectModel().getRemoteHspPath();
    this.service.getProductDataMap().forEach((value) => {
      for (const moduleTargetData of value) {
        const moduleName = moduleTargetData.getModuleModel().getName();
        const relatedEntryModules = moduleTargetData.getRelatedEntryModules();
        const moduleType = moduleTargetData.getModuleModel().getModuleType();
        if (ModuleTaskService.checkEntryModules(moduleType, relatedEntryModules)) {
          for (const entryName of relatedEntryModules!) {
            const hapPackInfoPathWithEntry: string = path.resolve(
              moduleTargetData.getPathInfo().getModuleBuildOutputPath(),
              entryName,
              BuildArtifactConst.PACK_INFO,
            );
            this.hapPackInfoPathMap.set(hapPackInfoPathWithEntry, moduleName);
          }
        } else {
          const hapPackInfoPath: string = path.resolve(moduleTargetData.getPathInfo().getModuleBuildOutputPath(), BuildArtifactConst.PACK_INFO);
          this.hapPackInfoPathMap.set(hapPackInfoPath, moduleName);
        }
        const moduleModel = moduleTargetData.getModuleModel();
        const moduleRemoteHspPath = moduleModel.getRemoteHspPath();
        const dependencyManager = new DependencyManager(this.service.getProjectModel().isFaMode(),
          moduleModel, this.service.getProjectModel());
        const { npmDependencies } = dependencyManager.collectAllDependencies();
        if (!this.remoteHspModuleSet.has(moduleModel.getName())) {
          initSignRemoteHspMap(moduleRemoteHspPath, this.allRemoteHspPathMap, npmDependencies);
          initSignRemoteHspMap(projectRemoteHspPath, this.allRemoteHspPathMap, npmDependencies);
          this.remoteHspModuleSet.add(moduleModel.getName());
        }
      }
    });
  }

  protected async doTaskAction(): Promise<void> {
    this.hapPackInfoPathMap.forEach((moduleName: string, hapPackInfoPath: string) => {
      this.mergeHapPackInfo(hapPackInfoPath, moduleName);
    });
    const allOhModulesHspPathList = [...this.allRemoteHspPathMap.values()];
    for (const { hspPath, hspFileName } of allOhModulesHspPathList) {
      const fileMap = await ZipUtil.readFileInZIP(hspPath, [BuildArtifactConst.PACK_INFO, CommonConst.MODULE_JSON]);
      const hspPackInfo = fileMap.get(BuildArtifactConst.PACK_INFO);
      const hspModuleJsonInfo = fileMap.get(CommonConst.MODULE_JSON);
      if (hspPackInfo && hspModuleJsonInfo) {
        this.mergeRemoteHspPackInfo(JSON.parse(hspPackInfo), JSON.parse(hspModuleJsonInfo).module.name, hspFileName);
      }
    }
    _log.anonymizeDebug('Project Pack Info:', this._projectPackInfoObj);
    fse.outputJSONSync(path.resolve(this.service.getPathInfo().getProjectOutputPath(), BuildArtifactConst.PACK_INFO), this._projectPackInfoObj, {
      spaces: '\t',
    }); // 此处需要格式化后的pack.info, 因为老版本的打包工具无法识别压缩后的json
    this.moduleSet.clear();
  }

  /**
   * 合并该product包含的模块对应的hap的packInfo信息
   *
   * @param hapPackInfoPath hap的packInfo路径
   * @param moduleName 需要合并的模块名
   */
  private mergeHapPackInfo(hapPackInfoPath: string, moduleName: string): void {
    if (fse.existsSync(hapPackInfoPath)) {
      const hapPackInfoObj: PackInfo.PackInfoObj = getJson5Obj(hapPackInfoPath);

      // 判断pack.info中的bundleName是否相同，如果不存在bundleName证明是集成态hsp，不写入app信息
      if (hapPackInfoObj.summary.app.bundleName) {
        this._projectPackInfoObj.summary.app = hapPackInfoObj.summary.app;
      }
      if (!this.moduleSet.has(moduleName)) {
        this._projectPackInfoObj.summary.modules.push(hapPackInfoObj.summary.modules[0]); // 只添加本模块的信息，防止重复
        this.moduleSet.add(moduleName);
      }
      this._projectPackInfoObj.packages = this._projectPackInfoObj.packages.concat(hapPackInfoObj.packages);
    }
  }

  /**
   * 合并该product包含的模块对应的remoteHsp的packInfo信息
   *
   * @param hspPackInfo remoteHsp的packInfo信息
   * @param moduleName 需要合并的模块名
   * @param hspFileName  远程hsp的文件名
   */
  private mergeRemoteHspPackInfo(hspPackInfo: PackInfo.PackInfoObj, moduleName: string, hspFileName: string): void {
    // 现在hsp、hap等包的pack.info的packages虽然是个数组，但是现状只会保存当前包的信息，所以取第一个对象去做处理
    hspPackInfo.packages[0].name = this.removeHspExtension(hspFileName);
    if (!this.moduleSet.has(moduleName)) {
      this._projectPackInfoObj.summary.modules.push(hspPackInfo.summary.modules[0]); // 只添加本模块的信息，防止重复
      this.moduleSet.add(moduleName);
    }
    this._projectPackInfoObj.packages = this._projectPackInfoObj.packages.concat(hspPackInfo.packages);
  }

  private removeHspExtension(filename: string): string {
    const hspExtension = BuildArtifactExtension.DOT_HSP;

    // 检查文件名是否以.hsp结尾
    if (filename.endsWith(hspExtension)) {
      // 去除.hsp后缀
      return filename.slice(0, -hspExtension.length);
    }

    // 如果文件名不以.hsp结尾，则直接返回原文件名
    return filename;
  }
}
